using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;

namespace DejaVuTocReader
{

        /**
     * This class implements a random access mapping of input streams and URL 
     * connections.  For input streams and non-http connections, the mapping 
     * is achieved by reading in all the data into memory.  Http streams are
     * mapped by reading in data blocks when needed.
     *
     * @author Bill C. Riemers
     * @version $Revision: 1.13 $
     */
    public class DataPool
    {
      //~ Static fields/initializers ---------------------------------------------

      /** The default size of each block. */
      public static int BLOCKSIZE = 8192;
        
      /** Object for caching raw data. ! */
      public static Hashtable cache = new Hashtable();

      //~ Instance fields --------------------------------------------------------

      // This contains the data we a buffering.
      private ArrayList buffer = new ArrayList();

      // The end of the stream, or a number larger than the end of the stream.
      private int endOffset=int.MaxValue;
      
      // The url we are reading.
      private Uri url=null;
      
      // The input stream we are reading.
      private InputStream input=null;
      
      // True if we might be able random access memory blocks on a server.
      private bool rangeAccepted=true;
      
      // The pointer for a simple cache of blocks reciently accessed.
      //private int cacheAccessIndex=0;

      // A simple cache of blocks accessed.
      //private Object [] cacheAccessArray=new Object[256];
      
      // The pointer for a simple cache of blocks created.
      //private int cacheCreatedIndex=0;
      
      // A simple cache of blocks created.
      //private Object [] cacheCreatedArray=new Object[256];
      
      // The largest end offset of read data.
      private int currentSize=0;
      
      //~ Constructors -----------------------------------------------------------

      /**
       * Creates a new DataPool object.
       */
      public DataPool()
      {
      }

      //~ Methods ----------------------------------------------------------------



      /**
       * Initialize this map to read the specified URL. If a cached DataPool for this 
       * URL exists, it will be returned.
       * 
       * @param url the URL to read
       * 
       * @return an initialized DataPool
       */
      public DataPool init(Uri url)
      {
          return this;
      }
      
      /**
       * Initialize this map to read the specified stream
       * 
       * @param input the InputStream to read
       * 
       * @return the initialized DataPool
       */
      public DataPool init(InputStream input)
      {
        this.input=input;
        rangeAccepted=false;
        return this;
      }
      
      /** 
       * Query the largest read end offset.
       *
       * @return the largest read end offset
       */
      public int getCurrentSize()
      {
        return currentSize;
      }
      
      /**
       * Request the specified block of data. Data may be buffered, or read.
       *
       * @param index the position of the block start position divided by BLOCKSIZE.
       * @param read True if unavailable blocks should be read from the data source.
       *
       * @return a byte array up to size BLOCKSIZE, or null if no data is available.
       */
      public byte [] getBlock(int index, bool read)
      {
        int start=index*BLOCKSIZE;
        if((index < 0)||(start >= endOffset))
        {
          return null;
        }
        if(index < buffer.Count)
        {
          Object block=buffer[index];
          if(block != null)
          {
            if(block.GetType().IsArray)
            {
              return (byte[])block;
            }
            
          }
        }
        return read?readBlock(index):null;
      }

      // Read the specified block of data.  Synchronization should happen prior to calling this
      // routine.  Data may be read either sequentially, or in random order if the server supports
      // http 1.1 range specifiers.
      private byte [] readBlock(int index)
      {
        byte [] retval=getBlock(index,false);
        if(retval == null)
        {
          int retry=0;
          int start=index*BLOCKSIZE;
          int end=(index+1)*BLOCKSIZE;
          InputStream input=this.input;
          for(;(input != null)&&(buffer.Count < index);input=this.input)
          {
            if(getBlock(buffer.Count, true) == null)
            {
              return null;
            }
          }
          while((start < endOffset) && (start < end) )
          {
            if(input == null)
            {
              
                end=start;
                setEndOffset(end);
                break;
            }
            if(retval == null)
            {
              retval=new byte[BLOCKSIZE]; 
            }
            for(int size = end-start;size > 0;size=start-end)
            {
              int offset=start%BLOCKSIZE;
              int len=0;
              try
              {
                len = input.read(retval, offset, size);
              }
              catch(Exception)
              {
                
                if(rangeAccepted&&(++retry < 10))
                {
                  try
                  {
                    input.close();
                  }
                  catch(Exception)
                  {
                      // ignored
                  }
                  input=null;
                  continue;
                }
                len=0;
              }
              retry=0;
              if(len <= 0)
              {
                try
                {
                  input.close();
                }
                catch(IOException)
                {
                    // ignored
                }
                input=null;
                this.input=null;
                end=start;
                setEndOffset(end);
                if(offset > 0)
                {
                  byte [] xretval=new byte[offset];
                  Array.Copy(retval, 0, xretval, 0, offset);
                  retval=xretval;
                }
                else
                {
                  retval=null;
                }
                break;
              }
              start+=len;
            }
          }
          if(retval != null)
          {
            if(buffer.Count <= index)
            {
              setSize(buffer,index+1);
            }
            if(rangeAccepted&&(index > 0))
            {
                buffer.Insert(index, retval); 
              //cacheCreatedArray[cacheCreatedIndex++%cacheCreatedArray.Length]=retval;
            }
            else
            {
              buffer.Insert(index,retval);          
            }
            if(end > currentSize)
            {
              currentSize=end;
            }
          }
        }
        return retval;
      }

    private object createSoftReference(object retval,object retval_2)
    {
 	    return null;
    }

      public static void setSize(ArrayList list,int size)
      {
        
        if (size >= list.Count)
            list.Capacity = size;
        else
        {
            for (int i = list.Count - 1; i >= size; i--)
            {
                list.RemoveAt(i);
            }
            list.Capacity = size;
        }
        
      }

      /**
       * Set the end position.  This value may only be reduced, never increased.
       *
       * @param offset new end offset
       */
      protected void setEndOffset(int offset)
      {
        if(offset < endOffset)
        {
          endOffset=offset;
          int size=(offset+BLOCKSIZE-1)/BLOCKSIZE;
          if(size > buffer.Count)
          {
            setSize(buffer,size);
          }
        }
      }
      
      /**
       * Query the size of this vector.
       *
       * @return the size of this vector
       */
      public int getEndOffset()
      {
        return endOffset;
      }

    }


    
}
